/*
变量提升：var声明的变量会被提升到作用域的顶部，但不会被赋值。let和const声明的变量会被提升，但在声明之前无法访问（暂时性死区）。
函数提升：函数声明会被提升到作用域的顶部，可以在声明之前调用。函数表达式的变量声明会被提升，但赋值部分不会被提升。
*/

/*
在 JavaScript 中有 

6 种不同的数据类型：
string
number
boolean
object
function
symbol

3 种对象类型：
Object
Date
Array

2 个不包含任何值的数据类型：
null
undefined
*/

/*
for..of和for..in均可迭代一个列表；但是用于迭代的值却不同
for..in迭代的是对象的 键 的列表，而for..of则迭代对象的键对应的值
*/

class A {
	name = "A";
	value = 1000;
	run() {
		console.log("i am running");
	}
	run(other) {
		console.log(`i am running with ${other}`);
	}
}
let a = new A();
a.run();

class B {
	constructor(name, value) {
		this.name = name;
		this.value = value;
	}
	say() {
		console.log(`{name: ${this.name}, value: ${this.value}}`);
	}
}
let b = new B();
b.say();
let b1 = new B('小王', 100);
b1.say();
console.log(`b.prototype:${b.pro}`)

function C(name, value) {
	this.name = name;
	this.value = value;
}
let c = new C('c', 999);

/*
typeof 返回一个值或变量的基本类型
instanceof 检查一个对象是否是一个特定的构造函数创建的，或者是否实现了一个特定的接口，
		   可以用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上，通俗说就是用于判断某个实例是否属于某构造函数。
*/
console.log(`typeof null: ${typeof null}`); //object
console.log(`typeof a: ${typeof a}, typeof b: ${typeof b}, typeof c: ${typeof c}`); //typeof a: object, typeof b: object, typeof c: object
console.log(`constructor a: ${a.constructor}, constructor b: ${b.constructor}, constructor c: ${c.constructor}`);

// 不能直接在构造函数上添加新属性
B.score = "100分";
console.log(`b.score: ${b.score}`); // b.score: undefined
// JavaScript 对象有一个指向一个原型对象的链。
// 当试图访问一个对象的属性时，它不仅仅在该对象上搜寻，还会搜寻该对象的原型，以及该对象的原型的原型，依次层层向上搜索，直到找到一个名字匹配的属性或到达原型链的末尾。
// 可以使用 prototype 属性就可以给对象的构造函数添加新的属性
B.prototype.language = "english";
console.log(`b.language: ${b.language}`); // b.language: english
b1.score = "100分";
console.log(`b1.score: ${b1.score}`);

let ss1 = '100.1';
console.log(`string to number: ${typeof Number(a)}`); //number
let ss2 = 100;
console.log(`number to string: ${typeof String(a)}`); //string
console.log(Number(10.99));

// 格式化日期
function formatDateTime(ssd) {
	return `${ssd.getFullYear()}-${ssd.getUTCMonth()+1}-${ssd.getUTCDate()} ${ssd.getHours()}:${ssd.getMinutes()}:${ssd.getSeconds()}`;
}
let ssd = new Date();
console.log(`now: ${formatDateTime(ssd)}`);

// 错误处理
try {
	// addd('a');
	throw "出错了";
} catch (e) {
	console.log(`error(name:${e.name}, message:${e.message})`);
} finally {
	// do something
}

//在常规的比较中，数据类型是被忽略的
console.log(`10 == '10': ${10 == '10'}, 10 === '10': ${10 === '10'}`); //10 == '10': true, 10 === '10': false

/*
JavaScript 中 this 不是固定不变的，它会随着执行环境的改变而改变。

可参考：https://juejin.cn/post/7366813022008508443?searchId=202407121411378F3C44CAA918201C56C8

在方法中，this 表示该方法所属的对象。
如果单独使用，this 表示全局对象。
在函数中，this 表示全局对象。
在函数中，在严格模式下，this 是未定义的(undefined)。
在事件中，this 表示接收事件的元素。
类似 call() 和 apply() 方法可以将 this 引用到任何对象。

箭头函数中没有this机制，所以箭头函数中的this是引用了它外层非箭头函数的this
*/
var sss = this; //单独使用 this，则它指向全局(Global)对象。
console.log(`sss: ${sss}`);
// 这种情况this也指向global
function foo() {
    let name = '大姚'
    console.log("foo(): "+ this.name);
}
foo(); // undefined
let obj22 = {
    name: 1,
    foo2: function () {
		// 函数中的this指向的是引用它的对象
        console.log(">>> obj22.foo2(): "+this.name);
    },
	foo3: function() {
		setTimeout(function() {
			// this 指向foo3
			console.log(">>> obj22.foo3(): "+this.name);
		});
	},
	foo4: function() {
		let self = this;
		setTimeout(function() {
			// this 指向foo3, 新增self来处理
			console.log(">>> obj22.foo4(): "+self.name);
		});
	},
	foo5: function() {
		// 箭头函数没有this这个机制，写在箭头函数中的this也是它外层非箭头函数的this。
		setTimeout(() => console.log(">>> obj22.foo5(): "+this.name));
	},
}
obj22.foo2(); // 1
obj22.foo3(); // undefined
obj22.foo4(); // 1
obj22.foo5(); // 1


var person = {
  firstName: "John",
  lastName : "Doe",
  id       : 5566,
  fullName : function() {
    return this.firstName + " " + this.lastName; //this 表示 person 对象。
  }
}

/*
JavaScript中apply、call、bind函数
*/
let name111 = 'window'
function getName(param1, param2) {
  console.log(`${this.name111}: ${param1}, ${param2}`)
}
let obj = {
  name111: 'easylee',
}
//apply和call非常类似，都是用于改变函数中this的指向，只是传入的参数不同，等于间接调用一个函数，也等于将这个函数绑定到一个指定的对象上
//当函数有多个参数时，call 是直接传入多个参数，而 apply 将多个参数组合成一个数组传输参数
getName.call(obj, 123, 23)
getName.apply(obj, [123, 23])
//bind，和前面两者主要的区别是，通过 bind 绑定的不会立即调用，而是返回一个新函数，然后需要手动调用这个新函数，来实现函数内部 this 的绑定
let fn = getName.bind(obj, 123, 23)    // 通过绑定创建一个新函数，然后再调用新函数
fn()


/*
ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。
let 声明的变量只在 let 命令所在的代码块内有效。
const 声明一个只读的常量，一旦声明，常量的值就不能改变。

在 ES6 之前，JavaScript 只有两种作用域： 全局变量 与 函数内的局部变量。是没有块级作用域的概念的。
*/

//使用 var 关键字重新声明变量可能会带来问题
//在块中重新声明变量也会重新声明块外的变量：
var xxx0 = 10;
{ 
    var xxx0 = 2;
}
console.log(`xxx0:${xxx0}`); //xxx0: 2
// let 关键字就可以解决这个问题，因为它只在 let 命令所在的代码块 {} 内有效。
let xxx00 = 10;
{
	let xxx00 = 2;
}
console.log(`xxx00: ${xxx00}`); //xxx00: 10
// 合法
var xxx1 = 2;
var xxx1 = 3;
console.log(`xxx1: ${xxx1}`);
// 不合法
// try {
// 	let xxx2 = 3;
// 	var xxx2 = 3;
// } catch (e) {
// 	console.log(e);
// }

var xxx11 = 5;
for (var xxx11 = 0; xxx11 < 10; xxx11++) {}
console.log(`xxx11: ${xxx11}`); //xxx11: 10
var xxx12 = 5;
for (let xxx12 = 0; xxx11 < 10; xxx12++) {}
console.log(`xxx12: ${xxx12}`); //xxx12: 5

// const并非真正的常量，可理解为引用不可变
const xxx13 = 1;
try {
	xxx13 = 2;
} catch (e) {
	console.log(e);
}
const xxx15 = {a:1}
xxx15.a = 2;
console.log(`xxx15: ${xxx15}`);

// var变量可以在使用后声明，也就是变量可以先使用再声明
xxx16 = 1;
var xxx16;
// let不行，下方报错
// xxx17 = 1;
// let xxx17;


/*
null 和 undefined

null是一个只有一个值的特殊类型。表示一个空对象引用。
undefined 是一个没有设置值的变量。

typeof undefined             // undefined
typeof null                  // object
null === undefined           // false
null == undefined            // true
*/
let cctt00;
let myNull = null;
if (!null) {
	console.log(">>> if (!null) => true");
}
if (!undefined) {
	console.log(">>> if (!undefined) => true");
}

console.log(`cctt00: ${cctt00}, myNull: ${myNull}`); //undefined null
console.log(` ${null == undefined},  ${null === undefined}`); //true, false

// 类继承及静态方法
class Animal {
	constructor(name) {
		this.name = name;
	}
	run() {
		console.log(`${this.name} is running`);
	}
}
class Dog extends Animal {
	jump() {
		console.log(`${this.name} is jumping`);
	}
	runAndJump() {
		super.run(); // 必须要有super和this，否则报错
		this.jump();
	}
}
let dog = new Dog('黑皮');
dog.age = 1;
dog.runAndJump();
console.log(`dog.age: ${dog.age}`);

console.log(`dog instanceof Animal: ${dog instanceof Animal}`); //true
console.log(`dog instanceof foo: ${dog instanceof foo}`); //false

/*
JavaScript严格模式（use strict）

"use strict" 指令只允许出现在脚本或函数的开头。
参考：https://www.runoob.com/js/js-strict.html
*/


/*
javascript浮点精度丢失问题
使用这些方法，仍然需要注意数值范围、舍入策略和比较运算等方面的问题，根据具体的应用场景进行适当的调整和处理。
*/
// 加法运算
function add(a, b) {
  const precision = Math.max(getPrecision(a), getPrecision(b));
  const multiplier = Math.pow(10, precision);
  return (Math.round(a * multiplier) + Math.round(b * multiplier)) / multiplier;
}
// 减法运算
function subtract(a, b) {
  return add(a, -b);
}
// 乘法运算
function multiply(a, b) {
  const precision = getPrecision(a) + getPrecision(b);
  const multiplier = Math.pow(10, precision);
  return (Math.round(a * multiplier) * Math.round(b * multiplier)) / (multiplier * multiplier);
}
// 除法运算
function divide(a, b) {
  const precision = getPrecision(a) - getPrecision(b);
  const divisor = Math.pow(10, precision);
  return (Math.round(a / b * divisor)) / divisor;
}
// 获取浮点数的小数位数
function getPrecision(num) {
  const str = String(num);
  const decimalIndex = str.indexOf('.');
  return decimalIndex === -1 ? 0 : str.length - decimalIndex - 1;
}
console.log(`0.1+0.2 === 0.3: ${0.1+0.2 === 0.3}`); //false
console.log(`add(0.1,0.2) === 0.3: ${add(0.1,0.2) === 0.3}`); //true


/*
ES6新特性

1、块级变量 let 和 const

2、解构赋值

3、新的原始数据类型 Symbol 

4、Map 和 Set

5、Proxy 与 Reflect : https://www.runoob.com/w3cnote/es6-reflect-proxy.html

6、字符串：
	6.1、新方法：includes('') startsWith('') endsWith('') repeat(5) padStart(5,'a') padEnd(5,'a')
	6.2、模板字符串： `模版：${aa}`

7、数值：
    7.1、二进制 0b10 八进制0o07
    7.2、新方法：https://www.runoob.com/w3cnote/es6-number.html

8、对象：
	8.1、对象字面量：允许对象的属性直接写变量，这时候属性名是变量名，属性值是变量值。
	等等： https://www.runoob.com/w3cnote/es6-object.html

9、函数：默认参数、不定参数、箭头函数

10、class 
	在ES6中，class (类)作为对象的模板被引入，可以通过 class 关键字定义类。
	class 的本质是 function。它可以看作一个语法糖，让对象原型的写法更加清晰、更像面向对象编程的语法。
	类定义不会被提升，这意味着，必须在访问前对类进行定义，否则就会报错。
	https://juejin.cn/post/6844903924290289671?searchId=20240711085633390CC0C697EEA57C3BB8
	10.1、类继承，子类 constructor 方法中必须有 super ，且必须出现在 this 之前
	10.2、getter/setter getter 与 setter 必须同级出现
	10.3、修饰器 类修饰｜方法修饰：目前还处于提案中，需要借助第三方插件才能用
		  Node.js原生不支持装饰器语法，可通过使用babel及相关插件来实现装饰器的功能。
		  修饰器只能用于“类”和类的方法，不能用于函数，因为存在函数提升。

11、模块化
	ES6 的模块自动开启严格模式，不管你有没有在模块头部加上 use strict;。
	模块中可以导入和导出各种类型的变量，如函数，对象，字符串，数字，布尔值，类等。
	每个模块都有自己的上下文，每一个模块内声明的变量都是局部变量，不会污染全局作用域。
	每一个模块只加载一次（是单例的）， 若再去加载同目录下同文件，直接从内存中读取。

	导出的函数声明与类声明必须要有名称（export default 命令另外考虑）。 
	不仅能导出声明还能导出引用（例如函数）。
	export 命令可以出现在模块的任何位置，但必需处于模块顶层。
	import 命令会提升到整个模块的头部，首先执行
	export default默认导出,值得注意的是,一个模块只能有一个默认导出

	Es Module 和 Commonjs 的区别可参考：《深入浅出es6模块化》https://juejin.cn/post/7166046272300777508?searchId=20240711130813A49A33348511A79D9D87

12、Promise异步编程

13、Generator函数：《Javascript（ES6）Generator 入门》https://juejin.cn/post/6844903638721134605?searchId=20240711134721BFCAF2F0C730708E9752
	JavaScript 的函数都会一直运行到 return 或函数结束。
	但对于 Generator 函数，会一直运行到 遇到 yield 或 return 或函数结束。
	与一般函数不同，Generator 函数一旦被调用，就会返回一个 Generator 对象。
	这个对象拥有 Generator Iterable，可以使用 next() 方法或 for…of 循环迭代它。

14、async/await：async 是 ES7 才有的与异步操作有关的关键字，和 Promise ， Generator 有很大关联的。
	首先 async/await 是一种用于处理异步操作的语法糖，在 ES2017 被引入，用于更简洁地编写基于 Promise 的链式回调代码
	使用 async/await 主要是避免了传统的回调函数或 Promise 链的嵌套
*/

// 解构赋值
let [ja, jb, jc] = [1, 2, 3];
console.log(`ja: ${ja}, jb: ${jb}, jc:${jc}`);
let { foo1, bar1 } = { foo1: 'aaa', bar1: 'bbb' };
console.log(`foo1: ${foo1}, bar1: ${bar1}`);
let { baz2 : foo2 } = { baz2 : 'ddd' };
console.log(`foo2: ${foo2}`);

// Symbol
const sy = Symbol("KK");
console.log(`sy: ${sy.toString()}, typepf(sy): ${typeof(sy)}`);
// 每一个 Symbol 的值都是不相等的
const MY_RED = Symbol('red');
const COLOR_RED = Symbol('red');
console.log(`MY_RED == COLOR_RED: ${MY_RED == COLOR_RED}`);//false
// Symbol 值作为属性名时，该属性是公有属性不是私有属性，可以在类的外部访问。
// 但是不会出现在 for...in 、 for...of 的循环中，也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。
// 如果要读取到一个对象的 Symbol 属性，可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到
let syyy = Symbol("key1");
let syyyObject = {};
syyyObject[syyy] = "kk";
console.log(`syyyObject[syyy]: ${syyyObject[syyy]}`);
for (let i in syyyObject) { // 无输出
  console.log(i);
}
// console.log(`Object.keys(syyyObject): ${Object.keys(syyyObject)}`); // []
// console.log(`Reflect.ownKeys(syyyObject): ${Reflect.ownKeys(syyyObject)}`); // [Symbol(key1)]

// Map 和 Set
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
console.log(`myMap.size: ${myMap.size} keys:${myMap.keys()}`)
for (var [key, value] of myMap) {
  console.log(key + " = " + value);
}

//... 展开操作符
var mySet1 = new Set([1, 2, 3]);
var mySet2 = new Set([2, 3, 4]);
var unionSet = new Set([...mySet1, ...mySet2]);
unionSet.add(9);
// 用...操作符，将 Set 转 Array
var myArrayFromSet = [...unionSet];
console.log(`myArrayFromSet: ${myArrayFromSet}`); // 1,2,3,4
// String 转 Set
var mySetFromString = new Set('hello');  
console.log(`myArrayFromSet: ${[...mySetFromString]}`); // h, e, l, o

//Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
//Proxy 可以对目标对象的读取、函数调用等操作进行拦截，然后进行操作处理。它不直接操作对象，而是像代理模式，通过对象的代理对象进行操作，在进行这些操作时，可以添加一些需要的额外操作。
//Reflect 可以用于获取目标对象的行为，它与 Object 类似，但是更易读，为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
let ptarget = {
    name: 'Tom',
    age: 24
}
let phandler = {
    get: function(target, key) {
        console.log('getting '+key);
        return target[key]; // 不是target.key
    },
    set: function(target, key, value) {
        console.log('setting '+key);
        target[key] = value;
    }
}
let proxy = new Proxy(ptarget, phandler)
proxy.name     // 实际执行 handler.get
proxy.age = 25 // 实际执行 handler.set
//ES6 中将 Object 的一些明显属于语言内部的方法移植到了 Reflect 对象上（当前某些方法会同时存在于 Object 和 Reflect 对象上），未来的新方法会只部署在 Reflect 对象上。
//Reflect 对象对某些方法的返回结果进行了修改，使其更合理。
//Reflect 对象使用函数的方式实现了 Object 的命令式操作。
let exam = {
    name: "Tom",
    age: 24,
    set info(value){
        return this.age = value;
    }
}
exam.age; // 24
Reflect.set(exam, 'age', 25); // true
exam.age; // 25
console.log(`exam.age: ${exam.age}`) //exam.age: 25

// ES6字符串相关
console.log(`'abc'.includes('b'): ${'abc'.includes('b')}`);
console.log(`'abc'.startsWith('b'): ${'abc'.startsWith('b')}`);
console.log(`'a'.padStart(5, '0'): ${'a'.padStart(5, '0')}`);

// ES6数值相关
let bInt = 0b10;
let oInt = 0o07;
console.log(`bInt: ${bInt}, oInt: ${oInt}`);

console.log(0.1+0.2 == 0.3); //false

// ES6函数相关
// 默认参数
function ffn(name, age=10) {
	return name+','+age;
}
console.log(`ffn('lee',18):${ffn('lee',18)}; ffn('kuo',''):${ffn('kuo','')}; ffn('hai'):${ffn('hai')}; `);
// 不定参数
function ffnn(...values){
    return values.length;
}
console.log(`ffnn(1,2):${ffnn(1,2)}; ffnn(1,2,3,4):${ffnn(1,2,3,4)}; `);
// 箭头函数
const fAdd1 = (a, b) => console.log(a+b);
fAdd1(5,7);
const fReduce1 = (a, b) => {
	let r = a-b;
	console.log(r);
}
fReduce1(10, 2);

//ES6 之前，JavaScript 的 this 对象一直很令人头大，回调函数，经常看到 var self = this 这样的代码，
//为了将外部 this 传递到回调函数中，那么有了箭头函数，就不需要这样做了，直接使用 this 就行。
// 回调函数
var PersonA = {
    'age1001': 18,
    'sayHello': function () {
      setTimeout(function () {
        console.log(this.age1001);
      });
    }
};
var age1001 = 20;
PersonA.sayHello();  // 20
 
var PersonB = {
    'age2': 18,
    'sayHello': function () {
      setTimeout(()=>{
        console.log(this.age2);
      });
    }
};
var age2 = 20;
PersonB.sayHello();  // 18

// ES6类相关
// 继承中 
class Thing0 {
	constructor(name) {
		this.name = name;
	}
}
class Table0 extends Thing0 {
	//子类 constructor 方法中必须有 super ，且必须出现在 this 之前。
	//调用父类构造函数,只能出现在子类的构造函数。
	constructor(name, height) {
		super(name);
		this.height = height;
	}
}
let table0 = new Table0('Table', 1.2);
console.log(`table0(name:${table0.name}, height:${table0.height})`);
// getter/ setter
// getter 与 setter 必须同级出现
class GetSetExample {
	constructor (a) {
		this._a = a;
	}
	get a() {
		console.log('getter invoked.');
		return this._a;
	}
	set a(a) {
		console.log('setter invoked.');
		this._a = a;
	}
}
let getSetObj = new GetSetExample(1);
getSetObj.a = 100;
console.log(`getSetObj.a: ${getSetObj.a}`);
// 修饰器
// 虽然Node.js原生不支持装饰器语法，但可以通过使用babel及相关插件来实现装饰器的功能。
// 以通过使用babel及相关插件来实现装饰器的功能。

// // 类修饰
// // 第一个参数target，指向类本身
// const classTestable = (target) => {
// 	target.isTestable = true;
// }
// // function classTestable(target) {
// // 	target.isTestable = true;
// // }
// console.log(`getSetObj.isTestable: ${getSetObj.isTestable}`); //undefined

// @classTestable
// class ClassDecoratorExam {}
// console.log(`ClassDecoratorExam.isTestable: ${ClassDecoratorExam.isTestable}`); //undefined
// let classDecoratorExam = new ClassDecoratorExam();
// console.log(`ClassDecoratorExam.isTestable: ${ClassDecoratorExam.isTestable}, classDecoratorExam.isTestable: ${classDecoratorExam.isTestable}`); //undefined

// // 方法修饰
// // 3个参数：target（类的原型对象）、name（修饰的属性名）、descriptor（该属性的描述对象）。
// const readOnly = (target, name, descriptor) {
// 	console.log(`${name}'s method decorator.`);
// 	descriptor.writable = false;
//     return descriptor; // 必须返回
// }
// // function logMethod(id) {
// //     console.log('evaluated logMethod'+id);
// //     return (target, name, desctiptor) => console.log('excuted         logMethod '+id);
// // }
// @classTestable
// class MethodDecoratorExam {
// 	@readOnly
//     sum(a, b) {
//         return a + b;
//     }
// }

// // 模块化
// let eVar1 = 1000;
// const eVar2 = 1;
// function eFun() {
// 	console.log('eFun');
// }
// // export
// export const eVar0 = 1000;
// export eVar2;
// export {eVar1 as kConstLimit, eFun};
// //默认导出,值得注意的是,一个模块只能有一个默认导出
// export default { name: 'moment' }
// export default function foo(x,y) {
//   return x+y
// }
// // import
// import {eVar0} from './test.js';
// import { default as foo, bar, baz } from "./foo.js";
// import foo, * as FOO from "./foo.js";

// Promise异步编程
// Promise 异步操作有三种状态：pending（进行中）、fulfilled（已成功）和 rejected（已失败）。除了异步操作的结果，任何其他操作都无法改变这个状态。
// Promise 对象只有：从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ，状态就不会再变了即 resolved（已定型）。
let promise = new Promise(function(resolve, reject) {
  // 异步操作代码
  resolve("操作成功");
});

promise.then((value) => {
  console.log(value); // 输出：操作成功
});
console.log('>>> Promise测试.....');
//通过 .then 形式添加的回调函数，不论什么时候，都会被调用，可多次调用。
new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000);
})
.then(result => {
    console.log(result); // 输出 1
    return result * 2;
})
.then(result => {
    console.log(result); // 输出 2
    return result * 3;
})
.then(result => {
    console.log(result); // 输出 6
    return result * 4;
})
.catch(error => {
    console.log('捕获到错误：', error);
});


// Generator函数
// Generator 每次调用 next()，函数会一直运行到下一个 yield，然后暂停执行。
// 语法上他们的标志是一个星号 *，function* X 和 function *X 的效果相同。
// Generator 函数返回 Generator 对象。要把 Generator 对象赋值到一个变量，才能方便地使用它的 next() 方法。 
// 如果没有把 Generator 分配给变量，对它调用 next() 总是只会运行到第一个 yield 表达式。
//
// Generator 函数的生命周期
// 每次运行到 yield，Generator 函数都会返回一个对象，该对象包含 yield 产生的值和当前 Generator 函数的状态。
// 类似地，运行到 return，可以得到 return 的值，并且 done 的状态为 true。
// 当 done 的状态为 true 时，意味着 Generator 函数已经运行完毕，后面的 yield 统统无效。

function *funcGen() {
 console.log("one");
 yield '1';
 console.log("two");
 yield '2'; 
 console.log("three");
 return '3';
}

console.log('>>> Generator函数测试开始');
let ffg = funcGen();
ffg.next(); //one {value: "1", done: false}
ffg.next(); //two {value: "2", done: false}
console.log(ffg.next()); //three {value: "3", done: true}
ffg.next(); //无效
ffg.next(); //无效

// yield使用场景举例：为不具备 Iterator 接口的对象提供遍历方法。
function* objectEntries(obj) {
    const propKeys = Reflect.ownKeys(obj);
    for (const propKey of propKeys) {
        yield [propKey, obj[propKey]];
    }
}
const jane = { first: 'Jane', last: 'Doe' };
for (const [key,value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`);
}

// yield* 表达式
// yield* 表达式表示 yield 返回一个遍历器对象，用于在 Generator 函数内部，调用另一个 Generator 函数。
function *g1() {
	yield 2;
	yield 3;
	yield 4;
}
function *g2() {
	yield 1;
	yield* g1();
	yield 5;
}
var gIte = g2();
console.log(gIte.next());
console.log(gIte.next());
console.log(gIte.next());
console.log(gIte.next());
console.log(gIte.next());
console.log(gIte.next());
// 带 return 的 yield* 与一般 yield* 有点不同。当 yield* 与 return 语句一起使用时，yield* 被赋 return 的值，
// 也就是整个 yield* function() 与其关联 Generator 函数的返回值相等。
function *genFuncChild() {
	yield 1;
	yield 2;
	return 'foo';
	yield 3; //note that this is ignored
}
function *genFuncMain() {
	const result = yield* genFuncChild();
	console.log(result);
	yield 'the end';
}
var ggI = genFuncMain();
console.log(ggI.next()); //{ value: 1, done: false }
console.log(ggI.next()); //{ value: 2, done: false }
console.log(ggI.next()); //foo { value: 'the end', done: false }
console.log(ggI.next()); //{ value: undefined, done: true }

// yield* 还有一个值得一提的用法，它可以遍历 iterable 对象，如 Array，String 和 Map。
function *genFunc() {
	yield* [1,2];
	yield* 'HI';
	yield* arguments;
}
let ggII = genFunc(3,4,5);
console.log(ggII.next());
console.log(ggII.next());
console.log(ggII.next());
console.log(ggII.next());
console.log(ggII.next());
console.log(ggII.next());
console.log(ggII.next());

console.log('>>> Generator函数测试结束');

// async/await 函数
async function helloAsync() {
	return "helloAsync";
}
console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}
helloAsync().then(v=>{
   console.log(v);         // helloAsync
});

function testAwait1(){
   return new Promise((resolve) => {
       setTimeout(function(){
          console.log("testAwait1");
          resolve();
       }, 100);
   });
}
function testAwait2(){
   return new Promise((resolve) => {
       setTimeout(function(){
          resolve('testAwait2');
       }, 100);
   });
}
async function helloAsync2(){
   await testAwait1();
   let result = await testAwait2();
   console.log(result);
   console.log("helloAsync2");
 }
helloAsync2();

